package drds.global_transaction.spring.annotation;

import drds.global_transaction.config.ConfigurationFactory;
import drds.global_transaction.global_transaction_manager.TransactionManagerClient;
import drds.global_transaction.global_transaction_manager.api.GlobalTransactionFailureHandler;
import drds.global_transaction.resource_manager.ResourceManagerClient;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.Advisor;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

@Slf4j
public class GlobalTransactionScanner extends AbstractAutoProxyCreator implements InitializingBean
{
	private static final long serialVersionUID = 1L;

	private static final int order_num = 1024;

	private static final Set<String> PROXYED_SET = new HashSet<>();

	private final String applicationId;
	private final String serviceGroup;
	private final boolean disableGlobalTransaction = ConfigurationFactory.getInstance().getBoolean("server_addresss_look_up_service.disableGlobalTransaction", false);
	private final GlobalTransactionFailureHandler globalTransactionFailureHandlerHook;
	private GlobalTransactionalInterceptor globalTransactionalInterceptor;

	public GlobalTransactionScanner(@NonNull String applicationId, @NonNull String serviceGroup, GlobalTransactionFailureHandler globalTransactionFailureHandlerHook)
	{
		setOrder(order_num);
		setProxyTargetClass(true);
		this.applicationId = applicationId;
		this.serviceGroup = serviceGroup;
		this.globalTransactionFailureHandlerHook = globalTransactionFailureHandlerHook;
	}

	@Override
	public void afterPropertiesSet()
	{
		if (disableGlobalTransaction)
		{
			return;
		}
		init();

	}

	private Class<?> findTargetClass(Object proxy) throws Exception
	{
		if (AopUtils.isAopProxy(proxy))
		{
			AdvisedSupport advised = getAdvisedSupport(proxy);
			Object target = advised.getTargetSource().getTarget();
			return findTargetClass(target);
		} else
		{
			return proxy.getClass();
		}
	}

	@Override
	protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource customTargetSource) throws BeansException
	{
		return new Object[] { globalTransactionalInterceptor };
	}

	private AdvisedSupport getAdvisedSupport(Object proxyObject) throws Exception
	{
		Field hField;
		if (AopUtils.isJdkDynamicProxy(proxyObject))
		{
			hField = proxyObject.getClass().getSuperclass().getDeclaredField("h");
		} else
		{
			hField = proxyObject.getClass().getDeclaredField("CGLIB$CALLBACK_0");
		}
		hField.setAccessible(true);
		Object object = hField.get(proxyObject);
		Field advisedField = object.getClass().getDeclaredField("advised");
		advisedField.setAccessible(true);
		return (AdvisedSupport) advisedField.get(object);
	}

	private void init()
	{
		TransactionManagerClient.init(applicationId, serviceGroup);
		ResourceManagerClient.init(applicationId, serviceGroup);
	}

	private GlobalTransactionMarkedMethod newGlobalTransactionMethod(GlobalTransaction globalTransaction, Method method)
	{
		return new GlobalTransactionMarkedMethod(globalTransaction, method);
	}

	@Override
	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey)
	{
		if (disableGlobalTransaction)
		{
			return bean;
		}
		try
		{
			synchronized (PROXYED_SET)
			{
				if (PROXYED_SET.contains(beanName))
				{
					return bean;
				}
				PROXYED_SET.add(beanName);
				Class<?> serviceInterface = findTargetClass(bean);
				Method[] methods = serviceInterface.getMethods();
				LinkedList<GlobalTransactionMarkedMethod> globalTransactionMarkedMethodList = new LinkedList<GlobalTransactionMarkedMethod>();
				for (Method method : methods)
				{
					GlobalTransaction globalTransaction = method.getAnnotation(GlobalTransaction.class);
					if (globalTransaction != null)
					{
						globalTransactionMarkedMethodList.add(newGlobalTransactionMethod(globalTransaction, method));
					}
				}
				if (globalTransactionMarkedMethodList.isEmpty())
				{
					return bean;
				}

				globalTransactionalInterceptor = new GlobalTransactionalInterceptor(globalTransactionFailureHandlerHook);
				if (!AopUtils.isAopProxy(bean))
				{
					bean = super.wrapIfNecessary(bean, beanName, cacheKey);
				} else
				{
					AdvisedSupport advisedSupport = getAdvisedSupport(bean);
					Advisor[] advisors = buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(null, null, null));
					for (Advisor advisor : advisors)
					{
						advisedSupport.addAdvisor(0, advisor);
					}
				}
				return bean;
			}
		} catch (Exception exx)
		{
			throw new RuntimeException(exx);
		}
	}
}
